package com.eascs.workflow.timeEffective.utils;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.eascs.app.log.Logger;
import com.eascs.app.log.LoggerFactory;
import com.eascs.workflow.common.entity.timeEffective.WorkdayEntity;
import com.eascs.workflow.common.vo.ModelResult;
import com.eascs.workflow.timeEffective.service.WorkdayServer;

/**
 * 有效工作日计算类
 * */
@Component
public class CalculateTimeUtils {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	private WorkdayServer workdayServer;
	
    SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); //这里的格式可以自己设置   
    SimpleDateFormat format_YMD = new SimpleDateFormat( "yyyy-MM-dd");   
    
    //设置上班时间：该处时间可以根据实际情况进行调整  
    private static final int abh = 9;//上午上班时间,小时  
    private static final int abm = 0;//上午上班时间,分钟  
    private static final int abs = 0;//上午上班时间,秒钟  
    
    private static final int aeh = 12;//上午下班时间，小时  
    private static final int aem = 0;//上午下班时间，分钟  
    private static final int aes = 0;//上午下班时间，秒钟  
    
    private static final int pbh = 13;//下午上班时间，小时  
    private static final int pbm = 0;//下午上班时间，分钟
    private static final int pbs = 0;//下午上班时间，秒钟
    
    private static final int peh = 18;//下午下班时间，小时  
    private static final int pem = 0;//下午下班时间，分钟
    private static final int pes = 0;//下午下班时间，秒钟
    
    private static final String ASC="ASC";//向前移动一天
   	private static final String DEC="DEC";//向后移动一天
	private static final int moveDay=10;//向前、向后移动天数
  
    float H1 = abh+(float)(abm+abs/60)/60;  //上午上班时间
    float H2 = aeh+(float)(aem+abs/60)/60;  //下午下班时间
    float H3 = pbh+(float)(pbm+abs/60)/60;  //下午上班时间
    float H4 = peh+(float)(pem+abs/60)/60;  //下午下班时间
  
    float hoursPerDay = H2-H1+H4-H3;//每天上班小时数  
    int daysPerWeek = 5;//每周工作天数  
    long milsecPerDay = 1000*60*60*24;//每天的毫秒数  
    float hoursPerWeek = hoursPerDay*daysPerWeek;//每星期小时数  
    
    
    /** 
     * 计算超时时间
     * 
     *	1.处理时效时长，拆分整日数和不足一日的小时数
	 *	2.预处理开始时间，使它在有效时间范围内
	 *	3.移动开始时间，去掉时效的整日部分，使开始、结束时间在同一天或者相邻的工作日
	 *	4.增加有效工作日小时数
     * 
     * @param Date beginTime    真实开始时间  
     * @param int  hours		时效时长  
     */
    public Date calculateOvertime(Date t, int hours){
    	if (t == null || hours == 0) {
			return null;
		}
    	
    	logger.info("设置的上班时间为："+abh+":"+abm+"-"+aeh+":"+aem+"   "+pbh+":"+pbm+"-"+peh+":"+pem);
    	logger.info("每天工作时间为："+hoursPerDay+"小时");
  
        logger.info("设置的开始时间为："+printDate(t));
        logger.info("设置的时效时长为："+hours+"小时");
        
        //处理时效时长，拆分整日数和不足一日的小时数
        
        int d   = 0;					//时效天数
        int day = Float.valueOf(hoursPerDay).intValue();
        int h   = hours%day;//时效小时数
        if (hours >= day) {
        	d = Integer.parseInt((hours-h)/day+"");
		}
        logger.info("时效时长拆分为："+d+"天"+h+"小时");
        
        //查询开始时间、结束时间段有多少有效工作日，排除调休、假节日情况
        Date end=new Date();
        end.setTime(t.getTime());
        List<WorkdayEntity> workDays = new ArrayList<WorkdayEntity>();//有效工作日
        
        int repeat=0;
        while(workDays.size() < d+1 && repeat < 3){
        	end.setTime(end.getTime()+moveDay*milsecPerDay);
            workDays = queryWorkDays(t,end);
            repeat++;
        }
        
        if (repeat == 3 && workDays.size() < d+1) {
      	  logger.info("计算错误：重试"+repeat+"次，依然不能获取"+(d+1)+"个有效工作时间【后台未录入有效工作日】");
      	  return null;
		}
        
        if (workDays == null || workDays.size() ==0 || workDays.size() < d+1) {
        	  logger.info("计算错误：有效工作时间获取失败【后台未录入有效工作日】");
        	  return null;
		}
        
        //对时间进行预处理  
        t = processBeginTime(workDays,t);  
        logger.info("预处理后的开始时间为："+printDate(t));
        
        //移动开始时间，去掉时效的整日部分，使开始、结束时间在同一天或者相邻的工作日
        int dayCounts = 0;
        while(dayCounts < d){  
            Date temp = new Date(t.getTime()+milsecPerDay);  
            temp = processBeginTime(workDays,temp);  
            temp.setHours(t.getHours());  
            temp.setMinutes(t.getMinutes());  
            temp.setSeconds(t.getSeconds());
            t = temp;  
            dayCounts++;  
        }
        logger.info("开始时间向后移动了"+dayCounts+"个有效工作日");
        logger.info("开始时间调整为："+printDate(t));
        
        Date t2 = new Date();//超时时间
        t2.setTime(t.getTime());
        
        //增加有效工作日小时数
        t = processAddTime(workDays,t,h);  
        logger.info("超时时间为："+printDate(t));
        
    	return t;
    }
    
    /** 
     * 计算有效工作时长
     * 
	 *	1.查询开始时间、结束时间段有多少有效工作日，排除调休、假节日情况
	 *	2.预处理开始时间、结束时间，使它们都在有效时间范围内
	 *	3.移动开始时间，使开始、结束时间在同一天或者相邻的工作日
	 *	4.分别计算同天或隔天的情况
     * 
     * @param Date beginTime    真实开始时间  
     * @param Date endTime		真实结束时间  
     * @return 
     */
    @SuppressWarnings("all")
    public float calculateHours(String id,Date t1, Date t2){
    	if (t1 == null || t2 == null) {
			return 0;
		}
    	
    	logger.info(id+"----设置的上班时间为："+abh+":"+abm+"-"+aeh+":"+aem+"   "+pbh+":"+pbm+"-"+peh+":"+pem);
    	logger.info(id+"----每天工作时间为："+hoursPerDay+"小时");
  
        logger.info(id+"----设置的开始时间为："+printDate(t1));
        logger.info(id+"----设置的结束时间为："+printDate(t2));
  
        //有效工作日
        List<WorkdayEntity> workDays = queryWorkDays(t1,t2);
        if (workDays == null || workDays.size() ==0 ) {
        	  logger.info(id+"----计算错误：有效工作时间获取为空");
        	  return 0;
		}
        
        //对时间进行预处理  
        t1 = processBeginTime(workDays,t1);  
        t2 = processEndTime(workDays,t2);  
          
        logger.info(id+"----预处理后的开始时间为："+printDate(t1));
        logger.info(id+"----预处理后的结束时间为："+printDate(t2));
          
        //若开始时间晚于结束时间，返回0  
        if(t1.getTime()>t2.getTime()){  
            logger.info(id+"----总工作时间为：0小时");
            return 0;  
        }  
          
        float totalHours = 0;  
        int dayCounts = 0;//包含无效工作日（周末、节假日）：记录开始时间和结束时间之间工作日天数  
        
        //调整开始时间，使得开始时间和结束时间在同一天，或者相邻的工作日内。  
        while(t1.getTime()<=t2.getTime()){  
            Date temp = new Date(t1.getTime()+milsecPerDay);  
            temp = processBeginTime(workDays,temp);  
            temp.setHours(t1.getHours());  
            temp.setMinutes(t1.getMinutes());  
            temp.setSeconds(t1.getSeconds());
            if(temp.getTime()>t2.getTime()){  
                break;  
            }else{  
                t1 = temp;  
                dayCounts++;  
            }  
        }  
        logger.info(id+"----开始时间向后移动了"+dayCounts+"个工作日");
        logger.info(id+"----开始时间调整为："+printDate(t1));
        totalHours += dayCounts * hoursPerDay;  
        
        int h1 = t1.getHours();  //调整后开始时间  ,小时
    	int m1 = t1.getMinutes();//调整后开始时间  ,分钟
    	int s1 = t1.getSeconds();//调整后开始时间  ,秒钟
    	int d1 = t1.getDay();      //调整后开始时间  ,天
    	int h2 = t2.getHours();  //真实结束时间  ,小时
    	int m2 = t2.getMinutes();//真实结束时间  ,分钟
    	int s2 = t2.getSeconds();//真实结束时间  ,秒钟
    	int d2 = t2.getDay();      //真实结束时间  ,天
          
        float hh1 = h1 + (float)(m1+s1/60)/60;  //调整后开始时间  ,小时+分钟+秒钟
        float hh2 = h2 + (float)(m2+s2/60)/60;  
          
        //处理开始结束是同一天的情况  
        if(d1 == d2){  
            float tt = 0;  
            tt = hh2 - hh1;  
            if( hh1 >= H1 && hh1 <= H2 && hh2 >= H3){  
                tt = tt - (H3-H2);  //午休处理
            }  
            totalHours += tt;  
        }else{  
            //处理开始结束不是同一天的情况  
            float tt1 = H4 - hh1;  //减掉夜间,下班后的时间
            float tt2 = hh2 - H1;  //减掉凌晨，上班前的时间
            if(hh1 <= H2){  
                tt1 = tt1 - (H3-H2);  //午休处理
            }  
            if(hh2 >= H3){  
                tt2 = tt2 - (H3-H2);  //午休处理
            }  
            totalHours += (tt1 + tt2);  
        }  
          
        logger.info(id+"----总工作时间为："+totalHours+"小时");
        return totalHours;  
    }
	
    
    /** 
     * 查询开始时间、结束时间段有多少有效工作日，排除调休、假节日情况
     * @param start,end 
     * @return 
     */
    private List<WorkdayEntity> queryWorkDays(Date start,Date end) {
    	ModelResult<List<WorkdayEntity>> modelResult=workdayServer.queryWorkDays(start, end);
    	if (modelResult.isSuccess()) {
    		return modelResult.getModel();
		}
    	return null; 
    }
    
    /** 
     * 获取开始时间、结束时间的间隔天数
     * @param start,end 
     * @return 
     */
    private int getWorkDaysInterval(List<WorkdayEntity> workDays) {
    	int size=workDays.size();
    	return size-1;
    }
    
    /** 
     * 判断是否为工作日
     * @param 
     * @return 
     */
    public Boolean isWorkDay(List<WorkdayEntity> workDays,Date t){
    	Boolean result=false;
    	String s=format_YMD.format(t);
    	
    	for (WorkdayEntity date : workDays) {
    		String workDay = format_YMD.format(date.getWorkday());
    		if (workDay.equals(s)) {
    			result = true;
    			break;
			}
		}
    	return result;
    }
    
    /** 
     * 向前、向后N天查询工作日，考虑有周末、国庆等
     * @param t		    开始时间
     * @param order   向前、向后 
     * @param moveDay 移动天数
     * @return 
     */
    public Date getWorkDay(Date t,String order,int moveDay){
    	Date result=null;
    	int count=0;
    	
    	Date t2 = null;
    	ModelResult<List<WorkdayEntity>> modelResult;
    	if (order.equals(ASC)) {
    		t2 = new Date(t.getTime() - milsecPerDay*moveDay);
    		modelResult = workdayServer.queryWorkDays(t2, t);
		}else{
			t2 =new Date(t.getTime() + milsecPerDay*moveDay);
			modelResult = workdayServer.queryWorkDays(t, t2);
		}
    	
    	List<WorkdayEntity> days=modelResult.getModel();
    	boolean flag=true;
    	while (count < moveDay & flag) {
			if (order.equals(ASC)) {
				t.setTime(t.getTime() - milsecPerDay);
			}else if (order.equals(DEC)){
				t.setTime(t.getTime() + milsecPerDay);
			}
			
			for (WorkdayEntity item : days) {
				String d1 = format_YMD.format(item.getWorkday());
				String d2 = format_YMD.format(t);
				if(d1.equals(d2)){
					result = item.getWorkday();
					flag=false;
					break;
				}
			}
			count++;
		}
    	return result;
    }
	
	/** 
     * 对结束时间进行预处理，使其处于工作日内的工作时间段内 
     * @param t 
     * @return 
     */  
    private Date processEndTime(List<WorkdayEntity> workDays,Date t) {
    	Calendar cal = Calendar.getInstance();
    	cal.setTime(t);
    	
    	int hours=cal.get(Calendar.HOUR_OF_DAY);
    	int minute = cal.get(Calendar.MINUTE);
    	int second = cal.get(Calendar.SECOND);  
    	
        float h = hours + (float)(minute+second/60)/60;  
  
        //若结束时间晚于下午下班时间，将其设置为下午下班时间  
        if(h >= H4){  
            cal.set(Calendar.HOUR_OF_DAY,peh);
        	cal.set(Calendar.MINUTE,pem);
        	cal.set(Calendar.SECOND,pes);
        }else {  
            //若结束时间介于中午休息时间，那么设置为上午下班时间  
            if(h >= H2 && h <= H3){  
                cal.set(Calendar.HOUR_OF_DAY,aeh);
            	cal.set(Calendar.MINUTE,aem);
            	cal.set(Calendar.SECOND,aes);
            }else{  
                //若结束时间早于上午上班时间，日期向前推一天，并将时间设置为下午下班时间  
                if(h < H1){
                    t.setTime(t.getTime()-milsecPerDay);  
                    cal.setTime(t);
                    cal.set(Calendar.HOUR_OF_DAY,peh);
                	cal.set(Calendar.MINUTE,pem);
                	cal.set(Calendar.SECOND,pes);
                }  
            }  
        }  
  
        //若结束时间是周末/节假日，那么将结束时间向前推移到最近的工作日的下午下班时间 
        Boolean flag = isWorkDay(workDays,t);
        if(!flag){
        	Date workDay=getWorkDay(t,ASC,moveDay);
            cal.setTime(workDay);
            cal.set(Calendar.HOUR_OF_DAY,peh);
        	cal.set(Calendar.MINUTE,pem);
        	cal.set(Calendar.SECOND,pes);
        }  
        return cal.getTime();  
    }  
  
    /** 
     * 对开始时间进行预处理 
     * @param t
     * @return 
     */  
    private Date processBeginTime(List<WorkdayEntity> workDays,Date t) {
    	Calendar cal = Calendar.getInstance();
    	cal.setTime(t);
    	
    	int hours=cal.get(Calendar.HOUR_OF_DAY);
    	int minute = cal.get(Calendar.MINUTE);
    	int second = cal.get(Calendar.SECOND);  
    	
        float h = hours + (float)(minute+second/60)/60;  
  
        //若开始时间晚于下午下班时间，将开始时间向后推一天  
        if(h >= H4){  
            t.setTime(t.getTime()+milsecPerDay);  
            cal.setTime(t);
            cal.set(Calendar.HOUR_OF_DAY,abh);
        	cal.set(Calendar.MINUTE,abm);
        	cal.set(Calendar.SECOND,abs);
        }else {  
            //若开始时间介于中午休息时间，那么设置为下午上班时间  
            if(h >= H2 && h <= H3){  
                cal.set(Calendar.HOUR_OF_DAY,pbh);
            	cal.set(Calendar.MINUTE,pbm);
            	cal.set(Calendar.SECOND,pbs);
            }else{  
                //若开始时间早于上午上班时间，将hour设置为上午上班时间  
                if(h < H1){
                    cal.set(Calendar.HOUR_OF_DAY,abh);
                	cal.set(Calendar.MINUTE,abm);
                	cal.set(Calendar.SECOND,abs);
                }  
            }  
        }  
  
        //若结束时间是周末/节假日，那么将结束时间向前推移到最近的工作日的下午下班时间 
        Boolean flag = isWorkDay(workDays,t);
        if(!flag){
        	Date workDay=getWorkDay(t,DEC,moveDay);
            cal.setTime(workDay);
            cal.set(Calendar.HOUR_OF_DAY,abh);
         	cal.set(Calendar.MINUTE,abm);
         	cal.set(Calendar.SECOND,abs);
        }  
        return cal.getTime();  
    }  
    
    private static long getTimeMillis(float hours){
    	
    	return Float.valueOf(hours*60*60*1000).longValue();
    }
    
    /** 
     * 增加有效工作日小时数
     * @param t 被增加的时间
     * @param e 增加的工作日小时数
     * @return 
     */  
    private Date processAddTime(List<WorkdayEntity> workDays,Date t,int e) {
    	Calendar cal = Calendar.getInstance();
    	cal.setTime(t);
    	
    	float real   = e;									//真实添加小时数
    	int   hours  = cal.get(Calendar.HOUR_OF_DAY);
    	int   minute = cal.get(Calendar.MINUTE);
    	int   second = cal.get(Calendar.SECOND);
    	
        float h      = hours + (float)(minute+second/60)/60; //开始时间 
        float eh	 = h + e;								 //预计结束时间
        
        //预处理开始时间 ,排除当天午休时间
        if (h <= H2 && eh > H2) {
        	h    += (H3 - H2);
        	real += (H3 - H2);
		}
          
        //处理开始结束不同天的情况  
        if(eh >= H4){										//若开始时间晚于下午下班时间，将开始时间向后推一天
        	float diff = eh - H4;
        	if (diff > (H3 - H2)) {
        		real += (24 - H4 + H1) +(H3 - H2);						//加次日午休时间
			}else{
				real += (24 - H4 + H1);
			}
        	
        	long  time = getTimeMillis(real);
        	cal.setTimeInMillis(cal.getTimeInMillis() + time);  
        }else {
        	//处理开始结束是同一天的情况
        	long  time = getTimeMillis(real);
        	cal.setTimeInMillis(cal.getTimeInMillis() + time);  
        }
        
        logger.info("预计移动"+e+"小时时效，实际向后移动了"+real+"个小时");
    	
    	int h1  = cal.get(Calendar.HOUR_OF_DAY);			//调整后开始时间  ,小时
    	int m1 = cal.get(Calendar.MINUTE);					//调整后开始时间  ,分钟
    	int s1 = cal.get(Calendar.SECOND);					//调整后开始时间  ,秒钟
  
        //若结束时间是周末/节假日，那么将结束时间向前推移到最近的工作日的下午下班时间 
        Boolean flag = isWorkDay(workDays,cal.getTime());
        if(!flag){
        	Date workDay=getWorkDay(cal.getTime(),DEC,moveDay);
            cal.setTime(workDay);
            cal.set(Calendar.HOUR_OF_DAY,h1);
         	cal.set(Calendar.MINUTE,m1);
         	cal.set(Calendar.SECOND,s1);
        }
        return cal.getTime();
    }

    
    /** 
     * 格式化输出时间： yyyy-mm-dd hh:mm:ss 星期x 
     * @param t 
     * @return 
     */  
    private String printDate(Date t) {  
        String str;  
        String xingqi = null;  
        switch (t.getDay()) {  
        case 0:  
            xingqi = "星期天";  
            break;  
        case 1:  
            xingqi = "星期一";  
            break;  
        case 2:  
            xingqi = "星期二";  
            break;  
        case 3:  
            xingqi = "星期三";  
            break;  
        case 4:  
            xingqi = "星期四";  
            break;  
        case 5:  
            xingqi = "星期五";  
            break;  
        case 6:  
            xingqi = "星期六";  
            break;  
        default:  
            break;  
        }  
        str = format.format(t)+"  "+xingqi;  
        return str;  
    }
    
    
}
